package controllers.validation;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import models.CNAverage;
import models.NodeConfig;
import models.SensorConfig;
import models.SensorData;
import models.UserConfig;
import play.Logger;
import play.jobs.Job;
import ch.bfh.abch.rems.REMSMulticastController;
import ch.bfh.abch.rems.sender.tinkerforge.TFMulticastSender;
import ch.bfh.abcm.rems.gateway.GateWayMessageType;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.sensors.tinkerforge.TFSensorType;
import ch.bfh.abcm.rems.tinkerforge.TFNodeAverageMessage;

public class ValidationJob extends Job {

    private TFMulticastSender sender;
    private String measurementpoint;
    private List<SensorConfig> sensors;
    private List<String> nodes;

    public ValidationJob(String measurementpoint) {
        this.measurementpoint = measurementpoint;
        sender = new REMSMulticastController().getMulticastSender();
    }

    public void doJob() throws Exception {
        ArrayList<Integer> averageList = new ArrayList<Integer>();
        sensors = SensorConfig.getSensors(measurementpoint);
        // Check if last sensor values are OUTDATED
        Logger.info("Check if last sensor values are OUTDATED");
        for (SensorConfig sensor : sensors) {
            SensorData measurements = SensorData.getLastMeasurement(sensor.getUid());
            if (!isLastValueSmallerMaxDiff(sensor.getUid(), measurements.getTimestamp())) {
                Logger.error("MaxTimeDiff reached for sensor: %s", sensor.getUid());
                sendGatewayMessage("Sensor " + sensor + " is not responding", GateWayMessageType.SYSTEM_ERROR);
                return;
            }
            averageList.add(medianLastFiveMeasurements(sensor.getUid()));
        }
        // Check if the median of the last five sensor values ist OUT OF RANGE
        // TODO: Not checked (empty, division by zero, ...)
        Logger.info("Check if the median of the last five sensor values ist OUT OF RANGE");
        TFSensorType sensorType = sensors.get(0).getSensorType();
        String maxdiffKey = sensorType + UserConfig.MAX_DIVERGENT_KEY_EXT;
        if (!checkMaxDivergent(averageList, maxdiffKey)) {
            return;
        }
        // Calculate node average
        calcAndSendNodeAverage(averageList, sensorType);
        // Check if the last node average of each node is OUTDATED
        Logger.info("Check if the last node average of each node is OUTDATED");
        nodes = NodeConfig.getNodes();
        averageList.clear();
        Logger.debug("Nodes: %s", nodes);
        for (String node : nodes) {
            CNAverage lastAverage = CNAverage.getLastCNAvaerage(node, measurementpoint);
            Logger.debug("Node --> : %s, CNAverage: %s", node, lastAverage);
            if (!isLastValueSmallerMaxDiff(node, lastAverage.getTimestamp())) {
                Logger.error("MaxTimeDiff reached for node average on node: %s", node);
                sendGatewayMessage("Node " + node + " is not responding", GateWayMessageType.SYSTEM_ERROR);
                return;
            }
            averageList.add(lastAverage.getNodeValue());
        }
        // Check if the node average is OUT OF RANGE
        Logger.info("Check if the node average is OUT OF RANGE");
        if (!checkMaxDivergent(averageList, maxdiffKey)) {
            return;
        }
        // Calculate effective average
        int average = calcEffektiveValue(averageList);
        // Check if the effective average is OUT OF RANGE
        Logger.info("Check if the effective average is OUT OF RANGE");
        if (!checkMaxDivergentUser(average, sensorType)) {
            Logger.error("MaxDivergentUser reached for host: %s", measurementpoint);
            sendGatewayMessage("Effective value out of range for measurement point " + measurementpoint,
                    GateWayMessageType.SYSTEM_ERROR);
            return;
        }
        // Send system OK
        Logger.info("Send system OK");
        sendGatewayMessage("ok", GateWayMessageType.SYSTEM_ERROR);
    }

    private void sendGatewayMessage(String text, GateWayMessageType type) {
        GatewayMessage message = new GatewayMessage();
        message.setMessageType(type);
        message.setMessage(text);
        sender.sendGateWayMessage(message);
    }

    private boolean checkMaxDivergentUser(int average, TFSensorType type) {
        UserConfig ucmin = UserConfig.getProperty(type + UserConfig.MIN_VALUE_USER_KEY_EXT);
        UserConfig ucmax = UserConfig.getProperty(type + UserConfig.MAX_VALUE_USER_KEY_EXT);
        int min = Integer.valueOf(ucmin.getPropertyValue());
        int max = Integer.valueOf(ucmax.getPropertyValue());
        return average > min && average < max;
    }

    // TODO: Not checked (empty, division by zero, ...)
    private int calcEffektiveValue(ArrayList<Integer> lastAverages) {
        int sum = 0;
        for (Integer integer : lastAverages) {
            sum += integer;
        }
        return sum / lastAverages.size();
    }

    private void calcAndSendNodeAverage(ArrayList<Integer> averageLastFive, TFSensorType sensorType)
            throws UnknownHostException {
        Collections.sort(averageLastFive);
        Integer nodeAverage = averageLastFive.get(1);
        String nodeIP = InetAddress.getLocalHost().getHostAddress();
        TFNodeAverageMessage tfNodeAverage = new TFNodeAverageMessage(nodeAverage, nodeIP, measurementpoint, sensorType);
        Logger.debug("Send node average: %s", tfNodeAverage.toString());
        sender.sendTFNodeAverageMessage(tfNodeAverage);
    }

    private boolean checkMaxDivergent(ArrayList<Integer> averageLastFive, String key) {
        int max = Collections.max(averageLastFive);
        int min = Collections.min(averageLastFive);
        int diff = max - min;
        String maxdiff = UserConfig.getProperty(key).getPropertyValue();
        Logger.debug("Max: %s, Min: %s, Diff: %s, MaxDiff: %s", max, min, diff, maxdiff);
        if (diff > Integer.valueOf(maxdiff)) {
            Logger.error("MaxDivergent reached for host: %s", measurementpoint);
            sendGatewayMessage("Max divergent reached for host: " + measurementpoint, GateWayMessageType.SYSTEM_ERROR);
            return false;
        }
        return true;
    }

    private Integer medianLastFiveMeasurements(String uid) {
        List<Integer> lastFiveSensorValues = SensorData.getLastFiveMeasurements(uid);
        Collections.sort(lastFiveSensorValues);
        Logger.debug("Sensor Uid: %s Median of last five: %s", uid, lastFiveSensorValues.get(2));
        return lastFiveSensorValues.get(2);
    }

    public boolean isLastValueSmallerMaxDiff(String id, long timestamp) {
        long now = new Date().getTime();
        UserConfig userconfig = UserConfig.getProperty(UserConfig.SENSOR_MAX_TIME_DIFF_MS);
        long max_diff = Long.valueOf(userconfig.getPropertyValue());
        long diff = now - timestamp;
        Logger.info("Id: %s Now: %s, MaxDiff: %s, Diff %s", id, now, max_diff, diff);
        return (diff < max_diff);
    }
}